#ifndef OPTFRAME_NSSEQPERIODICCROSS_HPP_
#define OPTFRAME_NSSEQPERIODICCROSS_HPP_

// Framework includes
#include "../OptFrame/Move.hpp"
#include "../OptFrame/NSSeq.hpp"

using namespace std;

// Working structure: vector<T>

class MoveVRPCross: public Move<RepPRV, MemPRVP>
{

protected:
	int r1, r2; // route 1 and route 2;
	int p1, p2; // position 1 and position 2 of cross in r1 end r2, respectively

	PRVPProblemInstance& pPRVP;

public:

	MoveVRPCross(PRVPProblemInstance& _pPRVP, int _r1, int _r2, int _p1, int _p2) :
		pPRVP(_pPRVP), r1(_r1), r2(_r2), p1(_p1), p2(_p2)
	{
	}

	virtual ~MoveVRPCross()
	{
	}

	int get_r1()
	{
		return r1;
	}

	int get_r2()
	{
		return r2;
	}

	int get_p1()
	{
		return p1;
	}

	int get_p2()
	{
		return p2;
	}

	bool canBeApplied(const RepPRV& rep)
	{
		bool all_positive = (r1 >= 0) && (r2 >= 0) && (p1 >= 0) && (p2 >= 0);
		return all_positive && (rep.size() >= 2) && (rep.at(r1).size() >= 0) && (rep.at(r2).size() >= 0);
	}

	virtual MoveVRPCross& apply(RepPRV& rep)
	{
		vector<int> cross_r1, cross_r2;

		//copiando as partes que serão trocadas
		cross_r1.assign(rep.at(r1).begin() + p1, rep.at(r1).end());
		cross_r2.assign(rep.at(r2).begin() + p2, rep.at(r2).end());

		//removendo as partes copiadas
		rep.at(r1).erase(rep.at(r1).begin() + p1, rep.at(r1).end());
		rep.at(r2).erase(rep.at(r2).begin() + p2, rep.at(r2).end());

		//inserindo as novas partes

		rep.at(r1).insert(rep.at(r1).end(), cross_r2.begin(), cross_r2.end());
		rep.at(r2).insert(rep.at(r2).end(), cross_r1.begin(), cross_r1.end());

		// p->p, r1->r2, r2->r1, c1->c2, c2->c1, reverse->reverse,
		return *new MoveVRPCross(pPRVP, r1, r2, p1, p2);
	}

	virtual MoveVRPCross& apply(MemPRVP& m, RepPRV& rep)
	{

		Matrix<double>& dist = (*pPRVP.dist);

		double cargaRparte1 = 0, cargaRparte2 = 0, cargaRparte3 = 0, cargaRparte4 = 0;
		for (int i = 0; i < p1; ++i)
		{
			cargaRparte1 += pPRVP.dados->at(rep.at(r1).at(i)).at(DEMANDA);
		}

		cargaRparte2 = m.getRouteLoad().at(r1) - cargaRparte1;

		for (int i = 0; i < p2; ++i)
		{
			cargaRparte3 += pPRVP.dados->at(rep.at(r2).at(i)).at(DEMANDA);
		}

		cargaRparte4 = m.getRouteLoad().at(r2) - cargaRparte3;

		double inf_antes = min(0.0, pPRVP.capacidade - (cargaRparte1 + cargaRparte2)) + min(0.0, pPRVP.capacidade - (cargaRparte3 + cargaRparte4));
		double inf_dps = max(0.0, (cargaRparte1 + cargaRparte4) - pPRVP.capacidade) + max(0.0, (cargaRparte2 + cargaRparte3) - pPRVP.capacidade);

		//calculo do custo do movimento
		double delta = 0;
		double delta_inf = inf_antes + inf_dps;

		//para facilitar as contas, adicionando o depósito no final das rotas
		rep.at(r1).push_back(0);
		rep.at(r2).push_back(0);

		//remocao dos arcos antigos
		if (p1 - 1 < 0)//deve considerar o depósito
			delta += -dist(0, rep.at(r1).at(p1));
		else
			delta += -dist(rep.at(r1).at(p1 - 1), rep.at(r1).at(p1));

		if (p2 - 1 < 0)//deve considerar o depósito
			delta += -dist(0, rep.at(r2).at(p2));
		else
			delta += -dist(rep.at(r2).at(p2 - 1), rep.at(r2).at(p2));

		//adicionando os novos arcos
		if (p2 - 1 < 0)
			delta += dist(0, rep.at(r1).at(p1));
		else
			delta += dist(rep.at(r2).at(p2 - 1), rep.at(r1).at(p1));

		if (p1 - 1 < 0)
			delta += dist(0, rep.at(r2).at(p2));
		else
			delta += dist(rep.at(r1).at(p1 - 1), rep.at(r2).at(p2));

		//removendo os depósitos que facilitaram a conta
		rep.at(r1).pop_back();
		rep.at(r2).pop_back();

		vector<int> cross_r1, cross_r2;

		//copiando as partes que serão trocadas
		cross_r1.assign(rep.at(r1).begin() + p1, rep.at(r1).end());
		cross_r2.assign(rep.at(r2).begin() + p2, rep.at(r2).end());

		//removendo as partes copiadas
		rep.at(r1).erase(rep.at(r1).begin() + p1, rep.at(r1).end());
		rep.at(r2).erase(rep.at(r2).begin() + p2, rep.at(r2).end());

		//inserindo as novas partes

		rep.at(r1).insert(rep.at(r1).end(), cross_r2.begin(), cross_r2.end());
		rep.at(r2).insert(rep.at(r2).end(), cross_r1.begin(), cross_r1.end());

		//setando as variações de fo e inviabilidade
		m.setDelta(delta);
		m.setDelta_inf(delta_inf);

		//atualizando a carga da memória

		m.getRouteLoad().at(r1) = cargaRparte1 + cargaRparte4;
		m.getRouteLoad().at(r2) = cargaRparte2 + cargaRparte3;

		// p->p, r1->r2, r2->r1, c1->c2, c2->c1, reverse->reverse,
		return *new MoveVRPCross(pPRVP, r1, r2, p1, p2);
	}

	virtual bool operator==(const Move<RepPRV, MemPRVP>& _m) const
	{
		const MoveVRPCross& m1 = (const MoveVRPCross&) _m;
		return ((m1.r1 == r1) && (m1.r2 == r2) && (m1.p1 == p1) && (m1.p2 == p2));
	}

	void print() const
	{
		cout << "MoveVRPCross( ";
		cout << r1 << ", " << r2 << ", " << p1 << ", " << p2 << " )";
		cout << endl;
	}
};

class NSIteratorVRPCross: public NSIterator<RepPRV, MemPRVP>
{

private:

	MoveVRPCross* m;
	vector<MoveVRPCross*> moves;
	int index; //index of moves
	const RepPRV& r;

	PRVPProblemInstance& pPRVP;

public:

	NSIteratorVRPCross(PRVPProblemInstance& _pPRVP, const RepPRV& _r) :
		pPRVP(_pPRVP), r(_r)
	{
		m = NULL;
		index = 0;
	}

	virtual ~NSIteratorVRPCross()
	{
		/*for (int i = 0; i < moves.size(); i++)
		 delete moves[i];*/
	}

	void first()
	{

		for (int r1 = 0; r1 < r.size() - 1; r1++)
		{
			for (int r2 = r1 + 1; r2 < r.size(); r2++)
			{
				for (int p1 = 0; p1 <= r.at(r1).size(); p1++)
				{
					for (int p2 = 0; p2 <= r.at(r2).size(); p2++)
					{
						moves.push_back(new MoveVRPCross(pPRVP, r1, r2, p1, p2));
					}
				}

			}
		}

		if (moves.size() > 0)
		{
			m = moves[index];
		}
		else
			m = NULL;
	}

	void next()
	{
		index++;
		if (index < moves.size())
		{
			m = moves[index];
		}
		else
			m = NULL;
	}

	bool isDone()
	{
		return m == NULL;
	}

	MoveVRPCross& current()
	{
		if (isDone())
		{
			cout << "There isnt any current element!" << endl;
			cout << "NSSeqVRPCross. Aborting." << endl;
			exit(1);
		}

		return *m;
	}
};

class NSSeqVRPCross: public NSSeq<RepPRV, MemPRVP>
{

private:
	PRVPProblemInstance& pPRVP;
public:

	NSSeqVRPCross(PRVPProblemInstance& _pPRVP) :
		pPRVP(_pPRVP)
	{
	}

	virtual ~NSSeqVRPCross()
	{
	}

	MoveVRPCross& move(const RepPRV& rep)
	{
		if (rep.size() < 2)
			return *new MoveVRPCross(pPRVP, -1, -1, -1, -1);

		int r1 = rand() % rep.size();

		int r2;

		do
		{
			r2 = rand() % rep.size();
		} while (r1 == r2);

		if ((rep.at(r2).size() == 0) && (rep.at(r1).size() == 0))
			return *new MoveVRPCross(pPRVP, -1, -1, -1, -1);

		int p1 = rand() % (rep.at(r1).size() + 1);
		int p2 = rand() % (rep.at(r2).size() + 1);

		return *new MoveVRPCross(pPRVP, r1, r2, p1, p2);
	}

	virtual NSIteratorVRPCross& getIterator(const RepPRV& r)
	{
		return *new NSIteratorVRPCross(pPRVP, r);
	}

	virtual void print()
	{
		cout << "NSSeqVRPCross" << endl;
	}
};

#endif /*OPTFRAME_NSSEQPERIODICCROSS_HPP_*/
